home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (c) 1987-1990 The Regents of the University of California.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that: (1) source code distributions
- * retain the above copyright notice and this paragraph in its entirety, (2)
- * distributions including binary code include the above copyright notice and
- * this paragraph in its entirety in the documentation or other materials
- * provided with the distribution, and (3) all advertising materials mentioning
- * features or use of this software display the following acknowledgement:
- * ``This product includes software developed by the University of California,
- * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
- * the University nor the names of its contributors may be used to endorse
- * or promote products derived from this software without specific prior
- * written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
- */
- #ifndef lint
- char copyright[] =
- "@(#) Copyright (c) 1987-1990 The Regents of the University of California.\nAll rights reserved.\n";
- static char rcsid[] =
- "@(#)$Header: tcpslice.c,v 1.10 92/06/02 17:57:44 mccanne Exp $ (LBL)";
- #endif
-
- /*
- * tcpslice - extract pieces of and/or glue together tcpdump files
- */
-
- #include <stdio.h>
- #include <ctype.h>
- #include <string.h>
- #include <sys/types.h>
- #include <sys/time.h>
- #include <sys/timeb.h>
- #include <netinet/in.h>
- #include <varargs.h>
-
- #include "savefile.h"
- #include "version.h"
-
-
- int tflag = 0; /* global that util routines are sensitive to */
-
- char *program_name;
-
- long thiszone; /* gmt to local correction in trace file */
-
- /* Length of saved portion of packet. */
- int snaplen;
-
- /* Length of saved portion of data past link level protocol. */
- int snapdlen;
-
- /* Precision of clock used to generate trace file. */
- int precision;
-
- static int linkinfo;
-
- /* Style in which to print timestamps; RAW is "secs.usecs"; READABLE is
- * ala the Unix "date" tool; and PARSEABLE is tcpslice's custom format,
- * designed to be easy to parse. The default is RAW.
- */
- enum stamp_styles { TIMESTAMP_RAW, TIMESTAMP_READABLE, TIMESTAMP_PARSEABLE };
- enum stamp_styles timestamp_style = TIMESTAMP_RAW;
-
-
- time_t gwtm2secs( /* struct tm *tmp */ );
-
-
- long local_time_zone( /* timestamp */ );
- struct timeval parse_time(/* time_string, base_time*/);
- void fill_tm(/* time_string, is_delta, t, usecs_addr */);
- void get_file_range( /* filename, first_time, last_time */ );
- struct timeval first_packet_time(/* filename */);
- void extract_slice(/* filename, start_time, stop_time */);
- char *timestamp_to_string( /* timestamp */ );
- void dump_times(/* filename */);
- void usage();
-
-
- int
- main(argc, argv)
- int argc;
- char **argv;
- {
- int op;
- int dump_flag = 0;
- int report_times = 0;
- char *start_time_string = 0;
- char *stop_time_string = 0;
- char *write_file_name = "-"; /* default is stdout */
- struct timeval first_time, start_time, stop_time;
-
- extern char *optarg;
- extern int optind, opterr;
-
- program_name = argv[0];
-
- opterr = 0;
- while ((op = getopt(argc, argv, "dRrtw:")) != EOF)
- switch (op) {
-
- case 'd':
- dump_flag = 1;
- break;
-
- case 'R':
- ++report_times;
- timestamp_style = TIMESTAMP_RAW;
- break;
-
- case 'r':
- ++report_times;
- timestamp_style = TIMESTAMP_READABLE;
- break;
-
- case 't':
- ++report_times;
- timestamp_style = TIMESTAMP_PARSEABLE;
- break;
-
- case 'w':
- write_file_name = optarg;
- break;
-
- default:
- usage();
- /* NOTREACHED */
- }
-
- if ( report_times > 1 )
- error( "only one of -R, -r, or -t can be specified" );
-
-
- if (optind < argc)
- /* See if the next argument looks like a possible
- * start time, and if so assume it is one.
- */
- if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
- start_time_string = argv[optind++];
-
- if (optind < argc)
- if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
- stop_time_string = argv[optind++];
-
-
- if (optind >= argc)
- error("at least one input file must be given");
-
-
- first_time = first_packet_time(argv[optind]);
- fclose( sf_readfile );
-
-
- if (start_time_string)
- start_time = parse_time(start_time_string, first_time);
- else
- start_time = first_time;
-
- if (stop_time_string)
- stop_time = parse_time(stop_time_string, start_time);
-
- else
- {
- stop_time = start_time;
- stop_time.tv_sec += 86400*3660; /* + 10 years; "forever" */
- }
-
-
- if (report_times) {
- for (; optind < argc; ++optind)
- dump_times(argv[optind]);
- }
-
- if (dump_flag) {
- printf( "start\t%s\nstop\t%s\n",
- timestamp_to_string( &start_time ),
- timestamp_to_string( &stop_time ) );
- }
-
- if (! report_times && ! dump_flag) {
- if ( ! strcmp( write_file_name, "-" ) &&
- isatty( fileno(stdout) ) )
- error("stdout is a terminal; redirect or use -w");
-
- sf_write_init(write_file_name, linkinfo, thiszone, snaplen,
- precision);
-
- for (; optind < argc; ++optind)
- extract_slice(argv[optind], &start_time, &stop_time);
-
- fclose( sf_writefile );
- }
-
- return 0;
- }
-
-
- /* Returns non-zero if a string matches the format for a timestamp,
- * 0 otherwise.
- */
- int is_timestamp( str )
- char *str;
- {
- while ( isdigit(*str) || *str == '.' )
- ++str;
-
- return *str == '\0';
- }
-
-
- /* Return the correction in seconds for the local time zone with respect
- * to Greenwich time.
- */
- long local_time_zone(timestamp)
- long timestamp;
- {
- struct timeval now;
- struct timezone tz;
- long localzone;
-
- if (gettimeofday(&now, &tz) < 0) {
- perror("tcpslice: gettimeofday");
- exit(1);
- }
- localzone = tz.tz_minuteswest * -60;
-
- if (localtime((time_t *) ×tamp)->tm_isdst)
- localzone += 3600;
-
- return localzone;
- }
-
- /* Given a string specifying a time (or a time offset) and a "base time"
- * from which to compute offsets and fill in defaults, returns a timeval
- * containing the specified time.
- */
-
- struct timeval
- parse_time(time_string, base_time)
- char *time_string;
- struct timeval base_time;
- {
- struct tm *bt = localtime((time_t *) &base_time.tv_sec);
- struct tm t;
- struct timeval result;
- time_t usecs = 0;
- int is_delta = (time_string[0] == '+');
-
- if ( is_delta )
- ++time_string; /* skip over '+' sign */
-
- if ( is_timestamp( time_string ) )
- { /* interpret as a raw timestamp or timestamp offset */
- char *time_ptr;
-
- result.tv_sec = atoi( time_string );
- time_ptr = strchr( time_string, '.' );
-
- if ( time_ptr )
- { /* microseconds are specified, too */
- int num_digits = strlen( time_ptr + 1 );
- result.tv_usec = atoi( time_ptr + 1 );
-
- /* turn 123.456 into 123 seconds plus 456000 usec */
- while ( num_digits++ < 6 )
- result.tv_usec *= 10;
- }
-
- else
- result.tv_usec = 0;
-
- if ( is_delta )
- {
- result.tv_sec += base_time.tv_sec;
- result.tv_usec += base_time.tv_usec;
-
- if ( result.tv_usec > 1000000 )
- {
- result.tv_usec -= 1000000;
- ++result.tv_sec;
- }
- }
-
- return result;
- }
-
- if (is_delta) {
- t = *bt;
- usecs = base_time.tv_usec;
- } else {
- /* Zero struct (easy way around lack of tm_gmtoff/tm_zone
- * under older systems) */
- bzero((char *)&t, sizeof(t));
-
- /* Set values to "not set" flag so we can later identify
- * and default them.
- */
- t.tm_sec = t.tm_min = t.tm_hour = t.tm_mday = t.tm_mon =
- t.tm_year = -1;
- }
-
- fill_tm(time_string, is_delta, &t, &usecs);
-
- /* Now until we reach a field that was specified, fill in the
- * missing fields from the base time.
- */
- #define CHECK_FIELD(field_name) \
- if (t.field_name < 0) \
- t.field_name = bt->field_name; \
- else \
- break
-
- do { /* bogus do-while loop so "break" in CHECK_FIELD will work */
- CHECK_FIELD(tm_year);
- CHECK_FIELD(tm_mon);
- CHECK_FIELD(tm_mday);
- CHECK_FIELD(tm_hour);
- CHECK_FIELD(tm_min);
- CHECK_FIELD(tm_sec);
- } while ( 0 );
-
- /* Set remaining unspecified fields to 0. */
- #define ZERO_FIELD_IF_NOT_SET(field_name,zero_val) \
- if (t.field_name < 0) \
- t.field_name = zero_val
-
- if (! is_delta) {
- ZERO_FIELD_IF_NOT_SET(tm_year,90); /* should never happen */
- ZERO_FIELD_IF_NOT_SET(tm_mon,0);
- ZERO_FIELD_IF_NOT_SET(tm_mday,1);
- ZERO_FIELD_IF_NOT_SET(tm_hour,0);
- ZERO_FIELD_IF_NOT_SET(tm_min,0);
- ZERO_FIELD_IF_NOT_SET(tm_sec,0);
- }
-
- result.tv_sec = gwtm2secs(&t);
- result.tv_sec -= local_time_zone(result.tv_sec);
- result.tv_usec = usecs;
-
- return result;
- }
-
-
- /* Fill in (or add to, if is_delta is true) the time values in the
- * tm struct "t" as specified by the time specified in the string
- * "time_string". "usecs_addr" is updated with the specified number
- * of microseconds, if any.
- */
- void
- fill_tm(time_string, is_delta, t, usecs_addr)
- char *time_string;
- int is_delta; /* if true, add times in instead of replacing */
- struct tm *t; /* tm struct to be filled from time_string */
- time_t *usecs_addr;
- {
- char *t_start, *t_stop, format_ch;
- int val;
-
- #define SET_VAL(lhs,rhs) \
- if (is_delta) \
- lhs += rhs; \
- else \
- lhs = rhs
-
- /* Loop through the time string parsing one specification at
- * a time. Each specification has the form <number><letter>
- * where <number> indicates the amount of time and <letter>
- * the units.
- */
- for (t_stop = t_start = time_string; *t_start; t_start = ++t_stop) {
- if (! isdigit(*t_start))
- error("bad date format %s, problem starting at %s",
- time_string, t_start);
-
- while (isdigit(*t_stop))
- ++t_stop;
- if (! t_stop)
- error("bad date format %s, problem starting at %s",
- time_string, t_start);
-
- val = atoi(t_start);
-
- format_ch = *t_stop;
- if ( isupper( format_ch ) )
- format_ch = tolower( format_ch );
-
- switch (format_ch) {
- case 'y':
- if ( val > 1900 )
- val -= 1900;
- SET_VAL(t->tm_year, val);
- break;
-
- case 'm':
- if (strchr(t_stop+1, 'D') ||
- strchr(t_stop+1, 'd'))
- /* it's months */
- SET_VAL(t->tm_mon, val - 1);
- else /* it's minutes */
- SET_VAL(t->tm_min, val);
- break;
-
- case 'd':
- SET_VAL(t->tm_mday, val);
- break;
-
- case 'h':
- SET_VAL(t->tm_hour, val);
- break;
-
- case 's':
- SET_VAL(t->tm_sec, val);
- break;
-
- case 'u':
- SET_VAL(*usecs_addr, val);
- break;
-
- default:
- error(
- "bad date format %s, problem starting at %s",
- time_string, t_start);
- }
- }
- }
-
-
- /* Return in first_time and last_time the timestamps of the first and
- * last packets in the given file.
- */
- void
- get_file_range( filename, first_time, last_time )
- char filename[];
- struct timeval *first_time;
- struct timeval *last_time;
- {
- *first_time = first_packet_time( filename );
-
- if ( ! sf_find_end( first_time, last_time ) )
- error( "couldn't find final packet in file %s", filename );
- }
-
-
- /* Returns the timestamp of the first packet in the given tcpdump save
- * file, which as a side-effect is initialized for further save-file
- * reading.
- */
-
- struct timeval
- first_packet_time(filename)
- char filename[];
- {
- struct packet_header hdr;
- u_char *buf;
-
- if (sf_read_init(filename, &linkinfo, &thiszone, &snaplen, &precision))
- error( "bad tcpdump file %s", filename );
-
- buf = (u_char *)malloc((unsigned)snaplen);
-
- if (sf_next_packet(&hdr, buf, snaplen))
- error( "bad status reading first packet in %s", filename );
-
- free((char *)buf);
-
- return hdr.ts;
- }
-
-
- /* Extract from the given file all packets with timestamps between
- * the two time values given (inclusive). These packets are written
- * to the save file output set up by a previous call to sf_write_init().
- * Upon return, start_time is adjusted to reflect a time just after
- * that of the last packet written to the output.
- */
-
- void
- extract_slice(filename, start_time, stop_time)
- char filename[];
- struct timeval *start_time;
- struct timeval *stop_time;
- {
- long start_pos, stop_pos;
- struct timeval file_start_time, file_stop_time;
- int status;
- struct packet_header hdr;
- u_char *buf;
-
-
- if (sf_read_init(filename, &linkinfo, &thiszone, &snaplen, &precision))
- error( "bad tcpdump file %s", filename );
-
- buf = (u_char *)malloc((unsigned)snaplen);
-
- start_pos = ftell( sf_readfile );
-
-
- if ( (status = sf_next_packet( &hdr, buf, snaplen )) )
- error( "bad status %d reading packet in %s",
- status, filename );
-
- file_start_time = hdr.ts;
-
-
- if ( ! sf_find_end( &file_start_time, &file_stop_time ) )
- error( "problems finding end packet of file %s",
- filename );
-
- stop_pos = ftell( sf_readfile );
-
-
- /* sf_find_packet() requires that the time it's passed as its last
- * argument be in the range [min_time, max_time], so we enforce
- * that constraint here.
- */
- if ( sf_timestamp_less_than( start_time, &file_start_time ) )
- *start_time = file_start_time;
-
- if ( sf_timestamp_less_than( &file_stop_time, start_time ) )
- return; /* there aren't any packets of interest in the file */
-
-
- sf_find_packet( &file_start_time, start_pos,
- &file_stop_time, stop_pos,
- start_time );
-
- for ( ; ; )
- {
- struct timeval *timestamp;
- status = sf_next_packet( &hdr, buf, snaplen );
-
- if ( status )
- {
- if ( status != SFERR_EOF )
- error( "bad status %d reading packet in %s",
- status, filename );
- break;
- }
-
- timestamp = &hdr.ts;
-
- if ( ! sf_timestamp_less_than( timestamp, start_time ) )
- { /* packet is recent enough */
- if ( sf_timestamp_less_than( stop_time, timestamp ) )
- /* We've gone beyond the end of the region
- * of interest ... We're done with this file.
- */
- break;
-
- sf_write( buf, timestamp, (int) hdr.len,
- (int) hdr.caplen );
- *start_time = *timestamp;
-
- /* We know that each packet is guaranteed to have
- * a unique timestamp, so we push forward the
- * allowed minimum time to weed out duplicate
- * packets.
- */
- ++start_time->tv_usec;
- }
- }
-
- fclose( sf_readfile );
- free( (char *) buf );
- }
-
-
- /* Translates a timestamp to the time format specified by the user.
- * Returns a pointer to the translation residing in a static buffer.
- * There are two such buffers, which are alternated on subseqeuent
- * calls, so two calls may be made to this routine without worrying
- * about the results of the first call being overwritten by the
- * results of the second.
- */
-
- char *
- timestamp_to_string(timestamp)
- struct timeval *timestamp;
- {
- struct tm *t;
- #define NUM_BUFFERS 2
- static char buffers[NUM_BUFFERS][128];
- static int buffer_to_use = 0;
- char *buf;
-
- buf = buffers[buffer_to_use];
- buffer_to_use = (buffer_to_use + 1) % NUM_BUFFERS;
-
- switch ( timestamp_style )
- {
- case TIMESTAMP_RAW:
- sprintf( buf, "%d.%d", timestamp->tv_sec, timestamp->tv_usec );
- break;
-
- case TIMESTAMP_READABLE:
- t = localtime((time_t *) ×tamp->tv_sec);
- strcpy( buf, asctime( t ) );
- buf[24] = '\0'; /* nuke final newline */
- break;
-
- case TIMESTAMP_PARSEABLE:
- t = localtime((time_t *) ×tamp->tv_sec);
- sprintf( buf, "%02dy%02dm%02dd%02dh%02dm%02ds%06du",
- t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour,
- t->tm_min, t->tm_sec, timestamp->tv_usec );
- break;
-
- }
-
- return buf;
- }
-
-
- /* Given a tcpdump save filename, reports on the times of the first
- * and last packets in the file.
- */
-
- void
- dump_times(filename)
- char filename[];
- {
- struct timeval first_time, last_time;
-
- get_file_range( filename, &first_time, &last_time );
-
- printf( "%s\t%s\t%s\n",
- filename,
- timestamp_to_string( &first_time ),
- timestamp_to_string( &last_time ) );
- }
-
- void
- usage()
- {
- (void)fprintf(stderr, "tcpslice for tcpdump version %d.%d\n",
- VERSION_MAJOR, VERSION_MINOR);
- (void)fprintf(stderr,
- "Usage: tcpslice [-dRrt] [-w file] [start-time [end-time]] file ... \n");
-
- exit(-1);
- }
-